/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.util.ntlm;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.spec.KeySpec;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import cgl.narada.util.Base64;

/**
 * Title:
 * Description:
 * Company:
 * @author John Yin
 */

public class NTLMType3Message extends NTLMMessage {

    private String m_domain;
    private String m_host;
    private String m_user;
    private String m_password;
    private byte[] m_nonce = null;

    public NTLMType3Message(String domain,String host,String user,String password,byte[] nonce) {
        m_domain = domain;
        m_host   = host;
        m_user   = user;
        m_password = password;
        m_nonce  = nonce;
    }

    /**
     * -------------------------------------------------------------------------
     */
    public byte[] export() throws IOException {
        byte[] common = super.export();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        output.write(common);


        // Offset is *2 due to unicode.
        int domainOffset = 64;
        int userOffset = domainOffset + m_domain.length() * 2;
        int hostOffset = userOffset + m_user.length() * 2;
        byte[] lanResponse = getLanManagerResponse();
        byte[] ntResponse  = getNTResponse();
        int lanManOffset = hostOffset + m_host.length() * 2;
        int ntOffset     = lanManOffset + lanResponse.length;

        // Total payload offset.
        int messageLen = (ntOffset + ntResponse.length);

        // Type 3
        output.write(3);

        // 3 0's.
        output.write(0); output.write(0); output.write(0);

        // LanManager response len: Alwasy 0x18. + offset
        writeLenOffset(24,lanManOffset,output);
        output.write(0); output.write(0);

        // NT response len: Alwasy 0x18. + offset
        writeLenOffset(24,ntOffset,output);
        output.write(0); output.write(0);

        // Domain len + offset.
        writeLenOffset(m_domain.length()*2,domainOffset,output);
        output.write(0); output.write(0);

        // User len + offset.
        writeLenOffset(m_user.length()*2,userOffset,output);
        output.write(0); output.write(0);

        // Host len + offset.
        writeLenOffset(m_host.length()*2,hostOffset,output);
        output.write(0); output.write(0); output.write(0);
        output.write(0); output.write(0); output.write(0);

        writeShort(messageLen,output);
        output.write(0); output.write(0);

        // Write 0x8201 flags.
        output.write(0x01); output.write(0x82);
        output.write(0); output.write(0);

        writeUnicode(m_domain.toUpperCase(),output);
        writeUnicode(m_user,output);
        writeUnicode(m_host.toUpperCase(),output);
        output.write(lanResponse);
        output.write(ntResponse);

        return output.toByteArray();
    }

    /**
     * -------------------------------------------------------------------------
     */
    public String exportBase64() throws IOException  {
        return Base64.encodeBytes(export(),false);
    }

    /**
     * -------------------------------------------------------------------------
     */
    private byte[] getLanManagerResponse() throws IOException {
        /* setup LanManager password */
        byte[] passw = m_password.toUpperCase().getBytes();
        byte[] lm_pw = new byte[14];
        int    len   = (passw.length > 14 ? 14: passw.length);

        System.arraycopy(passw,0,lm_pw,0,len);
        if (len < 14) {
            Arrays.fill(lm_pw,len,14,(byte)0);
        }

        /* create LanManager hashed password */
        byte magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
        byte[] lm_hpw = new byte[21];

        try {
            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
            SecretKey key = setupDESKey(lm_pw, 0);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal(magic, 0, 8, lm_hpw, 0);

            key = setupDESKey(lm_pw, 7);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal(magic, 0, 8, lm_hpw, 8);
        } catch (Exception e) {
            throw new IOException("Cipher Exception: "+e.getMessage());
        }

        Arrays.fill(lm_hpw,16,lm_hpw.length,(byte)0);

        //System.out.println("==========LanManager Password=========");
        //cgl.narada.test.NTLMTest.printBytes(lm_hpw);
        //System.out.println();

        return calcResponse(lm_hpw,m_nonce);
    }

   /**
     * -------------------------------------------------------------------------
     */
    private byte[] getNTResponse() throws IOException  {
        byte[] passw = m_password.getBytes();
        int    len   = passw.length;
        if (len > 14) len = 14;
        byte[] nt_pw = new byte[len*2];

        for (int idx=0; idx < passw.length && idx < 14; idx++)
        {
            nt_pw[2*idx]   = passw[idx];
            nt_pw[2*idx+1] = 0;
        }

        try {
            byte[] nt_hpw = new byte[21];
            byte[] hash = MessageDigest.getInstance("MD4").digest(nt_pw);
            System.arraycopy(hash,0,nt_hpw,0,hash.length);
            Arrays.fill(nt_hpw,hash.length,nt_hpw.length,(byte)0);
            //System.out.println("==========NT Password=========");
            //cgl.narada.test.NTLMTest.printBytes(nt_hpw);
            //System.out.println();

            return calcResponse(nt_hpw,m_nonce);
        } catch (Exception e) {
            throw new IOException("Message Digest Exception: "+e.getMessage());
        }
    }

    /**
     * -------------------------------------------------------------------------
     */
    private byte[] calcResponse(byte[] keys,byte[] plaintext) throws IOException {
        byte[] result = new byte[24];

        try {
            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");

            SecretKey key = setupDESKey(keys, 0);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal(plaintext, 0, 8, result, 0);

            key = setupDESKey(keys, 7);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal(plaintext, 0, 8, result, 8);

            key = setupDESKey(keys, 14);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal(plaintext, 0, 8, result, 16);

            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new IOException("Cipher Exception: "+e.getMessage());
        }
    }

   /**
     * -------------------------------------------------------------------------
     */
    private SecretKey setupDESKey(byte[] key,int offset) throws IOException {
        byte[] desKeyData = new byte[8];
        int[] tmp = new int[7];

        for(int i = 0; i < 7; i++)
        {
           tmp[i] = (int)key[offset + i] & 0xff;
        }

        desKeyData[0] = (byte)(tmp[0] >>> 1);
        desKeyData[1] = (byte)(((tmp[0] & 0x01) << 6) | (tmp[1] >>> 2));
        desKeyData[2] = (byte)(((tmp[1] & 0x03) << 5) | (tmp[2] >>> 3));
        desKeyData[3] = (byte)(((tmp[2] & 0x07) << 4) | (tmp[3] >>> 4));
        desKeyData[4] = (byte)(((tmp[3] & 0x0F) << 3) | (tmp[4] >>> 5));
        desKeyData[5] = (byte)(((tmp[4] & 0x1F) << 2) | (tmp[5] >>> 6));
        desKeyData[6] = (byte)(((tmp[5] & 0x3F) << 1) | (tmp[6] >>> 7));
        desKeyData[7] = (byte)(tmp[6] & 0x7F);

        for(int i=0; i<8; i++)
        {
           desKeyData[i] = (byte)(((int)desKeyData[i] & 0xff) << 1);
        }


        try {
            KeySpec desKeySpec = new DESKeySpec(desKeyData);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
            return secretKey;
        } catch (Exception e) {
            throw new IOException("DES Exception: "+e.getMessage());
        }
    }
}
